
/*
  Temperature Regulator 
  Measures with a NTC-resistor the temperature (of a Crockpot) 
  and switches it on an off accordingly.
  The temperature is shown on a LCD display
  Hannes Hassler 2013
  GNU GENERAL PUBLIC LICENSE (GPL3)
  http://www.gnu.org/licenses/gpl-3.0.html 
 */
 
#include "Wire.h"
#include <DS1107H.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>


//An analogous Pin
#define TEMPERATURE_PIN 0

#define COUNTER_LIMIT 1000
#define DISPLAY_COUNTER_LIMIT 1000
#define DISPLAY_ANZ 3

#define DISPLAY_TEMP_IND 0
#define DISPLAY_TIME_IND 1
#define DISPLAY_CALIB_IND 2

//Digital Pin fuer Taster
#define UP_PIN          13
#define DOWN_PIN        12
#define MENU_SWITCH_PIN 11

//EEPROM CalibrateTabSize
#define calibrateTabSize 20
#define calibrateTabOffset 1
#define stableTempTabSize 20
#define stableTempTabOffset 22

#define lastTemperatureTabSize 24
#define consideredStableDiff   0.2

//Digital Pin to put the SSR ON and OFF
#define SWITCH_PIN 6
#define LCD_RS_PIN   10
#define LCD_ENBL_PIN 7
#define LCD_D4_PIN   5
#define LCD_D5_PIN   4
#define LCD_D6_PIN   3
#define LCD_D7_PIN   2
#define LCD_COLUMNS  16
#define LCD_ROWS     2

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(LCD_RS_PIN, LCD_ENBL_PIN, LCD_D4_PIN, LCD_D5_PIN, LCD_D6_PIN, LCD_D7_PIN);

int     tempPinVal;
float   tempPinAverage;
float   tempPinMax=0;
float   tempPinMin=1023;
float   calcResist;
float   calcTemp;

char ch;

long numberOfSamples=1000;
int measurementRounds=1;

long counter=COUNTER_LIMIT;


int temperatureSample=100;

boolean switchOn;
boolean scheduleOn;

int onPercentage=75;
int calibOnPercentage=-1;
double zielTemperatur=20;
double zielPerc;
long mIter=0;

DS1107H clock;
int displayIndicator=0;

int menuPinVal;
int upPinVal;
unsigned long pressedButtonTime=0;
int downPinVal;

byte calibrateTabBtFabrik[] = 
						{ 
                          127+5,127+5,127+5,127+5,127+5, //[0,250]
                          127+5,127+5,127+5,127+5,127+5, //]250-500]
                          127+5,127+5,127+5,127+5,127+3, //]500,750]
                          127+3,127+3,127+3,127+3,127+3  //]750,1000]
                        };
                        
byte calibrateTabBt[calibrateTabSize];
boolean newCalibrateValue=false;
boolean showTempRegularly=true;

int measureIndex=0;
int measureCounter=0;
int measureCounterLimit=50;
float lastTemperatures[lastTemperatureTabSize];

int pCILength=9;
float pCI[] =    {0.0,    263.0,  300.0,   444.0,    580.0,   690.0,    750.0,  900,     1023.0  };
float pCf[]  =   {1.0,  1.134,  1.141,   1.154,    1.161,   1.164,    1.165,  1.166 ,  1.185};
String version="0.95";

byte lowbyte;
byte highbyte;
int  intFrom2Bytes;

float v14;
float testResult;

int measuredTemp10;
float measuredTemp;
float bestFactor;

void setup() {   
  
  pinMode(UP_PIN,INPUT);
  pinMode(DOWN_PIN,INPUT);
  pinMode(MENU_SWITCH_PIN,INPUT);
  
  pinMode(SWITCH_PIN, OUTPUT);  
  putSwitch(false);
  
  lcd.begin(LCD_COLUMNS, LCD_ROWS);
  lcd.print("Initializing...");
  
  Serial.begin(9600);  
  //to set the timer chip  
  /*
  byte second=0;
  byte minute=14;
  byte hour=8;
  byte dayOfWeek=5;
  byte dayOfMonth=12;
  byte month=9;
  byte year=13;
  clock.setDate(second,minute,hour,dayOfWeek,dayOfMonth,month,year);    
  */ 
  
  double testInput=2.0;
  double testResult;
     
  counter=-COUNTER_LIMIT;
  
  //read Values from eeprom
  /*for (int calibIdx=calibrateTabOffset;calibIdx<calibrateTabSize+calibrateTabOffset;calibIdx++) {
    int value= EEPROM.read(calibIdx);
    calibrateTabBt[calibIdx]=value;
    show("eeprom idx=",calibIdx);
    showln("value=",value);
  }*/
  
  
}

void loop() {
  
  static int v = 0; 
  
  if (zielTemperatur==-10.0 && calibOnPercentage!=10.0) {
   calibOnPercentage=10.0;
   showln("calibOnPercentage=",10);
   showTempRegularly=!showTempRegularly;   
  }

 if (Serial.available()) { 
      ch = Serial.read();  
      switch(ch) {
       case '0'...'9':
          v = v * 10 + ch - '0';
          break;
        case 'a':           
           //v = readVcc()/1000.0;
           //showln("readVcc=",v);
           v = analogRead(14);
           v14 = (1.1*v)/1023;           
           showln("read(14=",v14);
           v=0;
           break;
        case 'e':
          showln("test val:",v);                      
          v = 0; break;
        case 'F':
          //Set EEPROM to Fabrikswert
          showln("EEPROM Kalibriertabelle auf Standard rückgestellt:",0);            
          for (int eepIdx=calibrateTabOffset;eepIdx<calibrateTabSize+calibrateTabOffset;eepIdx++) {
              EEPROM.write(eepIdx,calibrateTabBtFabrik[eepIdx]);
          }
          showln("EEPROM rückgestellt fertig:",0);
          break;       
        case 'l':
          for (int mIdx=0; mIdx<lastTemperatureTabSize;mIdx++) {
             show("lastTemp",mIdx);
             showln("=",lastTemperatures[mIdx]);
          }     
        case 'm':
          showln("measure Rounds:",measurementRounds); 
        case 'M':
          measurementRounds=v;
          showln("new measure Rounds:",measurementRounds); 
          v=0;
          break;
        case 'n':
          showln("number of Samples:",numberOfSamples);                                
          break;
        case 'N':
          numberOfSamples=v;
          showln("new number of Samples:",numberOfSamples); 
          v=0;
          break;
        case 'o':
          calibOnPercentage=v;
          showln("calibOnPercentage=",v); 
          v=0;
          break;
        case 'O':
          calibOnPercentage=-1;
          showln("calibOnPercentage=",-1); 
          v=0;
          break;
       case 'p':
          onPercentage=v;
          showln("new onPercentage:",onPercentage); 
          v=0;
          break;
       case 'r':
          showTempRegularly=!showTempRegularly;
          showln("showTempRegularly:",showTempRegularly);
          break;
       case 's':
           zielTemperatur=v;
           zielPerc=stablePs(zielTemperatur);
           showln("P_s=",zielPerc);
           v=0;
           break;       
       case 't':
          measuredTemp10=v; 
          measuredTemp=(measuredTemp10)/10.0;                     
          bestFactor=diffactor((tempPinMax+tempPinMin)/2, measuredTemp);
          showln("bestFactor=",bestFactor*100);
          v=0;
          break;       
       case 'u':
          showln("tempPinVal=",tempPinVal);
          testResult=interPolPotCorr(tempPinVal)*100;          
          showln("calcActCorrFact*100=",testResult);          
          break;
      /*case 'q':
          lowbyte=intLowByte(v);
          highbyte=intHighByte(v);
          intFrom2Bytes=intFromBytes(highbyte, lowbyte);
          showln("intFromBytes=",intFrom2Bytes);
          v=0;
          break;
      */
      /*case 'z':
          zielTemperatur=v;
          showln("Zieltemperatur=",zielTemperatur);
          v=0;
          break;
      */
    }
  }  
    
  
  
  
  handleButtonTrigger();      
  
  putSwitch(isOn(onPercentage)&&scheduleOn);
  tempPinVal=analogRead(TEMPERATURE_PIN);
  delay(4);
  tempPinAverage=((tempPinAverage*temperatureSample)+(tempPinVal*interPolPotCorr(tempPinVal)))/(temperatureSample+1);
  tempPinMax=max(tempPinMax,tempPinVal);
  tempPinMin=min(tempPinMin,tempPinVal);
  
  
  if (counter>COUNTER_LIMIT) {     
    
      calcResist=calcR_NTC(tempPinAverage);  
      if (showTempRegularly) {    
        show("tempPinAverage=",tempPinAverage);
        show("\tcalibIdx=",getCalibrateIdx(tempPinAverage));
        showln("\tcorrVal=",calibrateTabBt[getCalibrateIdx(tempPinAverage)]-127);
        show("\ttempPinMin=",tempPinMin);
        showln("\ttempPinMax=",tempPinMax);
        tempPinMin=1023;
        tempPinMax=0;
        showln("Resist R_ntc=",calcResist);
      }
          
       
     calcTemp=calcR_NTC2TemperatureCelsius(calcResist);
     
     if (measureCounter>=measureCounterLimit) {
       lastTemperatures[measureIndex]=calcTemp;
       measureIndex=(measureIndex+1)%lastTemperatureTabSize;
       measureCounter=0;       
       if (maxDiffTempHistory()<consideredStableDiff) {
         showln("minDiff < 1.0 C",0);
       } else {
         showln("Diff. still@",maxDiffTempHistory());
       }
     } else {
       measureCounter++;
     }
               
     showDisplay();
     
     int zielTemperaturInt=zielTemperatur;
     
            
     
     boolean initialiSierung;
     if (calcTemp<=zielTemperatur-2.8)    {onPercentage=100;initialiSierung=true;}
     if (initialiSierung && calcTemp>zielTemperatur-2.8)   {onPercentage=stablePs(zielTemperatur);}
     if (calcTemp>zielTemperatur-0.7) {initialiSierung=false; onPercentage=stablePs(zielTemperatur);}
     if (!initialiSierung && calcTemp<zielTemperatur-1) {onPercentage=stablePs(zielTemperatur)+5;}
     if (calcTemp>zielTemperatur-0.2)   {onPercentage=0;}
     
     scheduleOn=isInSchedule();
     
     //45.65C
     if (calibOnPercentage>-1) {
        onPercentage=calibOnPercentage;
     }
     //onPercentage=10;
     
     //59.12<Temp<=59.99
     //onPercentage=20;
     
     //71.5<Temp<72.5
     //onPercentage=30;
     
     //82.2<Temp<82.3
     //onPercentage=40;
     
     clock.getDate();     
     
     if (showTempRegularly || measureCounter==0) {
       //show(" mIter=",mIter++);
       show(" actTemp=",calcTemp);
       showTime(clock);
     }
     
     //show(" Zieltemp=",zielTemperatur);
     //show(" onPerc=",onPercentage);
     //show(" scheduleON=",scheduleOn);
     
     
     
    counter=0;
  }  
  counter++;
             
}

float diffactor(float potAverage,  float measuredTemp) {
  float calcResist=calcR_NTC(potAverage*interPolPotCorr(potAverage));
  float calcTemp=calcR_NTC2TemperatureCelsius(calcResist); 
  showln("calTemp=",calcTemp);
  showln("measuredTemp=",measuredTemp);
  return measuredTemp/calcTemp;  
}

float bestfactor(float potAverage, float measuredTemp) {
      
      float minDiff=100;
      float bestFactor=-1; 
      
      for ( float factorTest=interPolPotCorr(potAverage)-0.1; 
                  factorTest<interPolPotCorr(potAverage)+0.1; 
                  factorTest=factorTest+0.001) {
                    
          float calcResist=calcR_NTC(potAverage*factorTest);
          float calcTemp=calcR_NTC2TemperatureCelsius(calcResist);
          if (abs(measuredTemp-calcTemp)<minDiff) {
             bestFactor=factorTest;
             minDiff=abs(measuredTemp-calcTemp);
          }
                
      }
      return bestFactor;
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
}

int intFromBytes(byte highbyte, byte lowbyte) {
  int highPart=highbyte;
      highPart=highPart<<8;
  return highPart+lowbyte;
}

byte intHighByte(int int2convert) {
  return (byte) (int2convert>>8);
}

byte intLowByte(int int2convert) {
  return (byte) int2convert;
}

float maxDiffTempHistory() {
    float tempMin=200.0;
    float tempMax=-200.0;
    for (int histIdx=0;histIdx<lastTemperatureTabSize;histIdx++) {
      if (lastTemperatures[histIdx]<tempMin) {tempMin=lastTemperatures[histIdx];}
      if (lastTemperatures[histIdx]>tempMax) {tempMax=lastTemperatures[histIdx];}      
    }
    return tempMax-tempMin;
}

void showDisplay() {
  
     switch(displayIndicator) {
       case DISPLAY_TEMP_IND:{print2lcd("aktuell  Ziel", calcTemp,zielTemperatur, 10, " C");} break;
       case DISPLAY_TIME_IND:printDate2lcd("Zeit", clock); break;
       case DISPLAY_CALIB_IND:{
             print2lcd("Kalibrierung", calcTemp,(calibrateTabBt[getCalibrateIdx(tempPinVal)]-127)/10.0, 10, ""); break;
       }
     } 
  
}

void calibTabIncDec(int upDown) {    
     int tabIdx=getCalibrateIdx(tempPinVal); 
     calibrateTabBt[tabIdx]=calibrateTabBt[tabIdx]+(upDown);
     if (upDown>0) {
       for (int calIdx=tabIdx+1;calIdx<calibrateTabSize;calIdx++) {
         if (calibrateTabBt[calIdx-1]>calibrateTabBt[calIdx]) {
              calibrateTabBt[calIdx]=calibrateTabBt[calIdx-1];
         }
       }       
     } else {
       for (int calIdx= tabIdx-1;calIdx>0;calIdx--) {
         if (calibrateTabBt[calIdx+1]<calibrateTabBt[calIdx]) {
              calibrateTabBt[calIdx]=calibrateTabBt[calIdx+1];
         }
       }   
     }
     newCalibrateValue=true;
}



void handleButtonTrigger() {
  
  menuPinVal = pinValTrigger2UP(menuPinVal&1, MENU_SWITCH_PIN);
  upPinVal   = pinValTrigger2UP(upPinVal&1,UP_PIN);
  downPinVal = pinValTrigger2UP(downPinVal&1, DOWN_PIN);
  
  if (menuPinVal==1) {  if (newCalibrateValue && displayIndicator==DISPLAY_CALIB_IND) {
                           for (int eepIdx=calibrateTabOffset;eepIdx<calibrateTabSize+calibrateTabOffset;eepIdx++) {
                                EEPROM.write(eepIdx,calibrateTabBt[eepIdx]);
                            }
                            newCalibrateValue=false;
                        }
                        displayIndicator=(displayIndicator+1)%DISPLAY_ANZ;
                        showDisplay();
                     }
                     
  if (upPinVal==1) {//showln("upButton pressed ", 0);
                     switch(displayIndicator) {
                       case DISPLAY_TEMP_IND:{zielTemperatur=zielTemperatur+0.5;} break;
                       case DISPLAY_TIME_IND:{printDate2lcd("Zeit", clock);} break;
                       case DISPLAY_CALIB_IND:{calibTabIncDec(1);} break;
                     }
                     pressedButtonTime=millis();
                     showDisplay();
                    }
  if (downPinVal==1) {//showln("downButton pressed ", 0);
                      switch(displayIndicator) {
                       case DISPLAY_TEMP_IND:{zielTemperatur=zielTemperatur-0.5;;} break;
                       case DISPLAY_TIME_IND:{}; break;
                       case DISPLAY_CALIB_IND:{calibTabIncDec(-1);} break;
                       }
                       pressedButtonTime=millis();
                       showDisplay();
                      }
         
             
  if (upPinVal==3 && (millis()-pressedButtonTime)>500) {
                          switch(displayIndicator) {
                           case DISPLAY_TEMP_IND:{zielTemperatur=zielTemperatur+0.5;} break;
                           case DISPLAY_TIME_IND:{printDate2lcd("Zeit", clock);} break;                          
                          }
                          showDisplay();
  }
  
  if (downPinVal==3 && (millis()-pressedButtonTime)>500) {
                          switch(displayIndicator) {
                           case DISPLAY_TEMP_IND:{zielTemperatur=zielTemperatur-0.5;} break;
                           case DISPLAY_TIME_IND:{printDate2lcd("Zeit", clock);} break;
                          }
                          showDisplay();
  }
  
  if (menuPinVal==0 && upPinVal==0 && downPinVal==0) {pressedButtonTime=0;}   
  
      
}



int pinValTrigger2UP(int currVal, int PIN) {
   int pinVal=digitalRead(PIN);
   int retVal=(currVal<<1)+pinVal;   
   return retVal;
}

void printDate2lcd(String title, DS1107H timerChip) {
   
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 0);
  lcd.print(title);
  lcd.setCursor(0, 1);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  
  lcd.print (timerChip.hour);
  lcd.print (":");
  lcd.print (timerChip.minute);
  lcd.print ("h");
  
}

void print2lcd(String title, float value1, float value2, int precision, String finalString) {
  
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 0);
  lcd.print(title);
  lcd.setCursor(0, 1);
  lcd.print("                     ");
  lcd.setCursor(0, 1);
  lcd.print("  ");
  printFloat2lcd(value1,precision);
  lcd.print("  ");
  printFloat2lcd(value2,precision);
    
  lcd.print(finalString);
  
}

void printFloat2lcd(float value,int precision) {
  
  if (value<0) {
    lcd.print("-");
  }
  lcd.print (int(value));  //prints the int part
  lcd.print(".");          // print the decimal point
  unsigned int frac;
  if(value >= 0)
        frac = (value - int(value)) * precision;
  else
        frac = (int(value)- value ) * precision;
  
  lcd.print(frac);
  
}





float stablePs(float targetTemp) {
  
  float retVal;
  if (targetTemp<40.85)                        {retVal=P_s(targetTemp,  0.0 ,10.0, 23.00,  40.85);}
  if (40.85<=targetTemp  && targetTemp<=55.05) {retVal=P_s(targetTemp, 10.0 ,20.0, 40.85,  55.05);}
  if (55.05<=targetTemp  && targetTemp<=68.25) {retVal=P_s(targetTemp, 20.0 ,30.0, 55.05,  68.25);}
  if (68.25<=targetTemp  && targetTemp<=77.00) {retVal=P_s(targetTemp, 30.0 ,40.0, 68.25,  77.00);}  
  if (77.00<targetTemp                      )  {retVal=P_s(targetTemp, 40.0 ,50.0, 77.00,  90.00);} 
  
  return retVal;
}



float t_k(float a, float b, float T1, float T2) {
  return (b-a)/(T2-T1);
}

float t_d(float a, float b, float T1, float T2) {
  return (a*T2-b*T1)/(T2-T1);
}

float P_s(float T_s, float a, float b, float T1, float T2) {
  return t_k(a, b, T1, T2)*T_s+t_d(a, b, T1, T2);
}


float interPolPotCorr(int measuredPot) {
  
  float retVal;  
  /*
  float measuredPotFloat=measuredPot;
  for (int potIdx=1; potIdx<pCILength-1; potIdx++) {
      if (pCI[potIdx-1]<=measuredPotFloat && measuredPotFloat<=pCI[potIdx]) {        
        float T1=pCI[potIdx-1];
        float T2=pCI[potIdx];
        float a=pCf[potIdx-1];
        float b=pCf[potIdx]; 
        float T_s=measuredPotFloat;        
        retVal=(  ((b-a)/(T2-T1))  *T_s) +  ((a*T2-b*T1)/(T2-T1));        
      }
  }*/
  float measuredPotFl=measuredPot;
  retVal=log(measuredPotFl)*0.184;
;
  return retVal;
}


boolean isInSchedule() {
  boolean retVal=false;
  clock.getDate();
  if (clock.hour>=6 && clock.hour<9) {retVal=true;}
  //if (clock.hour>=23) {retVal=true;}
  retVal=true;
  return retVal;
}


boolean isOn(long onPercentage) {
  unsigned long time = millis();
  
  //long partOfMinute= (time/1000)%(60);
  //return ((5*partOfMinute/3)<=onPercentage);
  
  long partOf10Sec= (time%10000)/100;  
  return (partOf10Sec<onPercentage);
  
}

void setEinschaltVerhaeltnis(int prozentsatz) {
  onPercentage=prozentsatz;
}


void putSwitch(boolean valueOn) {
     switchOn=valueOn;     
   if (switchOn) {
     digitalWrite(SWITCH_PIN, HIGH);
   } else {
     digitalWrite(SWITCH_PIN, LOW);
   }
}

void showln(char nameStr[], double val) {
  Serial.print(nameStr);    
  Serial.println(val);
  
}

void show(char nameStr[], double val) {
  Serial.print(nameStr);
  Serial.print(val);
}



void showTime(DS1107H timerChip) {
  Serial.print("   UT ");
  Serial.print(timerChip.hour, DEC);
  Serial.print(":");
  Serial.print(timerChip.minute, DEC);
  Serial.print(":");
  Serial.print(timerChip.second, DEC);
  Serial.print("  ");
  Serial.print(timerChip.dayOfMonth, DEC);
   Serial.print("/");
  Serial.print(timerChip.month, DEC); 
  Serial.print("/");
  Serial.print(timerChip.year, DEC);
  Serial.print("  Day_of_week:");
  Serial.println(timerChip.dayOfWeek, DEC);  
}



/*
float potentialCorrection (float rawPotential) {
  float correction=0;
      
  //Linear formula, derived from above empirical Data  
  correction=(rawPotential*0.06)-38.28;
  
  return correction;
}*/

int getCalibrateIdx(float potential) {
    return int (potential/50);
}

float potCorr(float measuredPotential) {
  
}

float calcR_NTC(float potential) {        
  //float potentialCorr=potential*1.135; //für 60 Grad
  //float potentialCorr=potential*1.13;// für 45 Grad
  //float potentialCorr=potential*1.097;// für 31 Grad
  //float potentialCorr=potential*1.11;// für 31 Grad
  //float potentialCorr=potential*1.07;// für 15 bis 20 Grad
  byte corrDelta=calibrateTabBt[getCalibrateIdx(potential)]-127; 
  corrDelta=0;  
  float fixWiderstand=50000-((corrDelta)*100);
  float messBereich=1023;
  return (messBereich-potential)*fixWiderstand/potential;
}


float calcR_NTC2TemperatureCelsius(float r_ntc)   {
  return calcR_NTC2TemperatureKelvin(r_ntc)-273.15; 
}


float calcR_NTC2TemperatureKelvin(float r_ntc) {
  float R_N=101800; //resistance at 23°C;
  float K_N=296.95; //Temperature at 23,8°C in Kelvin
  float B_N=3950;   //NTC-coefficient of the specifically used NTC  
  return B_N*K_N/(B_N+(log(r_ntc/R_N)*K_N));
}




